﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using StyleMVVM.Data;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.ViewModel;
using StyleMVVM.Data;
#if !DOT_NET
using Windows.Foundation.Metadata;
#endif
#if NETFX_CORE
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

#else
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

#endif

namespace StyleMVVM.View.Impl
{
	/// <summary>
	/// Default implementation of INavigationService
	/// </summary>
	public sealed class NavigationService : INavigationService
	{
		private static readonly string supplementalString = typeof(NavigationService).FullName;
		private Brush overlayBackground;
		private WeakReference<Grid> overlayReference;
		private readonly WeakReference<Frame> frameReference = null;
		private IViewBinderService viewBinder;

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(NavigationService)).
			          As(typeof(INavigationService)).
			          ImportProperty("Container").
			          ImportMethod("ImportDependencies");
		}

		public NavigationService()
		{
			if (ViewHelper.MainContent != null)
			{
				frameReference = new WeakReference<Frame>(ViewHelper.MainContent as Frame);
			}
		}

		public NavigationService(object frame)
		{
			frameReference = new WeakReference<Frame>(frame as Frame);
		}

		public IDependencyInjectionContainer Container { get; set; }

		public void ImportDependencies(IViewBinderService viewBinder)
		{
			this.viewBinder = viewBinder;
		}

		public Brush OverlayBackground
		{
			get
			{
				Brush returnValue = null;
				if (overlayBackground != null)
				{
					return overlayBackground;
				}
				else
				{
					try
					{
						returnValue =
							Application.Current.Resources["OverlayBackground"] as Brush;
					}
					catch (Exception)
					{
					}
				}

				if (returnValue == null)
				{
#if WINDOWS_PHONE
					returnValue = new SolidColorBrush(Colors.Cyan);
#else
					returnValue = new SolidColorBrush(Colors.LightCyan);
#endif
					returnValue.Opacity = 0.6;
				}

				return returnValue;
			}
			set
			{
				if (overlayBackground != value)
				{
					overlayBackground = value;

					if (overlayReference != null)
					{
						Grid overlay;

						if (overlayReference.TryGetTarget(out overlay))
						{
							overlay.Background = value;
						}
					}

					OnPropertyChanged();
				}
			}
		}

		public Frame Frame
		{
			get
			{
				Frame returnValue;

				frameReference.TryGetTarget(out returnValue);

				return returnValue;
			}
		}

		public bool IsValid
		{
			get
			{
				Frame outFrame = null;

				return frameReference.TryGetTarget(out outFrame);
			}
		}

		public bool CanGoBack
		{
			get
			{
				Frame frame = Frame;

				if (frame != null && !OverlayOpen)
				{
					return frame.CanGoBack;
				}

				return false;
			}
		}

		public bool CanGoForward
		{
			get
			{
				Frame frame = Frame;

				if (frame != null && !OverlayOpen)
				{
					return frame.CanGoForward;
				}

				return false;
			}
		}

		public void GoHome()
		{
			Frame frame = Frame;

			if (frame != null)
			{
				while (frame.CanGoBack)
				{
					frame.GoBack();
				}
			}
		}

		public void GoForward()
		{
			Frame frame = Frame;

			if (frame != null)
			{
				frame.GoForward();
			}
		}

		public void GoBack()
		{
			Frame frame = Frame;

			if (frame != null)
			{
				frame.GoBack();
			}
		}

#if !WINDOWS_PHONE
		[DefaultOverload]
		public bool Navigate(string pageName)
		{
			return Navigate(pageName, null);
		}

		public bool Navigate(Type pageType)
		{
			return Navigate(pageType, null);
		}

		[DefaultOverload]
		public bool Navigate(string pageName, object navigationParam)
		{
			bool returnValue = false;

			if (pageName == null)
			{
				throw new ArgumentNullException("pageName");
			}

			Frame frame = Frame;

			if (frame != null)
			{
				Logger.Info("Navigating to page name: " + pageName, supplementalString);

#if NETFX_CORE
				Type pageType = Container.LocateExportType(pageName);

				if (pageType == null)
				{
					throw new Exception("Could not find Page: " + pageName);
				}

				Logger.Debug(string.Format("Page name {0} resolved to type {1} ", pageName, pageType.FullName), supplementalString);

				returnValue = frame.Navigate(pageType, navigationParam);
#else
				// WPF
				object newPage = Container.Locate(pageName);

				if (newPage == null)
				{
					throw new Exception("Could not find Page: " + pageName);
				}

				returnValue = frame.Navigate(newPage, navigationParam);
#endif
			}

			if (returnValue && frame.Content != null)
			{
				viewBinder.Bind(frame.Content, navigationParam);
			}

			return returnValue;
		}

		public bool Navigate(Type pageType, object navigationParam)
		{
			bool returnValue = false;

			if (pageType == null)
			{
				throw new ArgumentNullException("pageType");
			}

			Frame frame = Frame;

			if (frame != null)
			{
				Logger.Info("Navigating to page type: " + pageType.FullName, supplementalString);

				returnValue = frame.Navigate(pageType, navigationParam);
			}

			if (returnValue && frame.Content != null)
			{
				viewBinder.Bind(frame.Content, navigationParam);
			}

			return returnValue;
		}
#else
		public bool Navigate(Uri pageUri)
		{
			Frame frame = Frame;

			if (frame != null)
			{
				return frame.Navigate(pageUri);
			}

			return false;
		}
#endif

		public bool OverlayOpen
		{
			get { return overlayReference != null; }
		}

		public bool OpenOverlay(string overlayName)
		{
			return OpenOverlay(overlayName, null);
		}

		public bool OpenOverlay(string overlayName, object context)
		{
			if (!OverlayOpen)
			{
				Grid overlayGrid = getChildGridFromFrame(Frame);

				if (overlayGrid != null)
				{
					createOverlay(overlayGrid, null, overlayName, context);

					OnPropertyChanged("OverlayOpen");
					OnPropertyChanged("CanGoBack");
					OnPropertyChanged("CanGoForward");
				}
			}

			return false;
		}

		public bool ReplaceOverlay(string newOverlayName)
		{
			return ReplaceOverlay(newOverlayName, null);
		}

		public bool ReplaceOverlay(string newOverlayName, object context)
		{
			Grid overlay = null;

			if (overlayReference != null && overlayReference.TryGetTarget(out overlay))
			{
				Grid overlayGrid = getChildGridFromFrame(Frame);

				if (overlayGrid != null)
				{
					if (overlay.Children.Count > 0)
					{
						FrameworkElement frameworkElement = overlay.Children[0] as FrameworkElement;

						if (frameworkElement != null)
						{
							if (frameworkElement.DataContext is IViewAware)
							{
								((IViewAware)frameworkElement.DataContext).View = null;
							}

							if (frameworkElement.DataContext is IOverlayViewModel)
							{
								((IOverlayViewModel)frameworkElement.DataContext).Navigation = null;
								((IOverlayViewModel)frameworkElement.DataContext).OverlayRemoved();
							}
						}
					}

					overlayGrid.Children.Clear();

					createOverlay(overlayGrid, overlayGrid, newOverlayName, context);
				}
			}

			return false;
		}

		public bool CloseOverlay()
		{
			Grid overlay = null;

			if (overlayReference != null && overlayReference.TryGetTarget(out overlay))
			{
				Grid overlayGrid = getChildGridFromFrame(Frame);

				if (overlayGrid != null)
				{
					overlayGrid.Children.Remove(overlay);

					if (overlay.Children.Count > 0)
					{
						FrameworkElement frameworkElement = overlay.Children[0] as FrameworkElement;

						if (frameworkElement != null)
						{
							if (frameworkElement.DataContext is IViewAware)
							{
								((IViewAware)frameworkElement.DataContext).View = null;
							}

							if (frameworkElement.DataContext is IOverlayViewModel)
							{
								((IOverlayViewModel)frameworkElement.DataContext).OverlayRemoved();
							}
						}
					}

					overlayReference = null;

					OnPropertyChanged("OverlayOpen");
					OnPropertyChanged("CanGoBack");
					OnPropertyChanged("CanGoForward");

					return true;
				}
			}
			else
			{
				overlayReference = null;

				OnPropertyChanged("OverlayOpen");
				OnPropertyChanged("CanGoBack");
				OnPropertyChanged("CanGoForward");
			}

			return false;
		}

		private void createOverlay(Grid pageGrid, Grid overlayGrid, string overlayName, object context)
		{
			FrameworkElement overlay = Container.Locate(overlayName) as FrameworkElement;

			if (overlay != null)
			{
				if (overlayGrid == null)
				{
					overlayGrid = new Grid();
				}

				overlayReference = new WeakReference<Grid>(overlayGrid);

				overlayGrid.Background = new SolidColorBrush(Colors.LightGray);
				overlayGrid.Background.Opacity = 0.6;

				overlay.HorizontalAlignment = HorizontalAlignment.Center;
				overlay.VerticalAlignment = VerticalAlignment.Center;

				overlayGrid.Children.Add(overlay);

				int colSpan = pageGrid.ColumnDefinitions.Count;
				int rowSpan = pageGrid.RowDefinitions.Count;

				Grid.SetColumn(overlayGrid, 0);

				if (colSpan > 0)
				{
					Grid.SetColumnSpan(overlayGrid, colSpan);
				}

				Grid.SetRow(overlayGrid, 0);

				if (rowSpan > 0)
				{
					Grid.SetRowSpan(overlayGrid, rowSpan);
				}

				pageGrid.Children.Add(overlayGrid);

				if (overlay.DataContext is IViewAware)
				{
					((IViewAware)overlay.DataContext).View = overlay;
				}

				if (overlay.DataContext is IOverlayViewModel)
				{
					IOverlayViewModel overlayViewModel =
						overlay.DataContext as IOverlayViewModel;

					overlayViewModel.Navigation = this;
					overlayViewModel.NavigationParameter = context;

					overlayViewModel.OverlayOpened();
				}
			}
			else
			{
				throw new Exception("Could not find overlay: " + overlayName);
			}
		}

		private Grid getChildGridFromFrame(Frame frame)
		{
			Grid returnValue = null;

			if (frame != null &&
			    frame.Content is Page && ((Page)frame.Content).Content is Grid)
			{
				return ((Page)frame.Content).Content as Grid;
			}

			return returnValue;
		}

		public event PropertyChangedEventHandler PropertyChanged;

		private void OnPropertyChanged([CallerMemberName] string propertyName = null)
		{
			if (PropertyChanged != null)
			{
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			}
		}
	}
}